<template>
  <el-dialog :append-to-body='true' :model-value="modelValue" :width="width" title="模型预览" @close="cleanup()"
             @opened="open">
    <div style="display: flex;">
      <div ref="container"></div>
      <div
          style="display: flex; flex-direction: column;justify-content: space-evenly; align-items: center;margin-left: 1rem;">
        <el-text tag="b">
          <el-icon>
            <InfoFilled/>
          </el-icon>
          按住鼠标左键旋转
        </el-text>
        <el-text tag="b">
          <el-icon>
            <InfoFilled/>
          </el-icon>
          按住Ctrl进行平移
        </el-text>
        <el-text tag="b">
          <el-icon>
            <InfoFilled/>
          </el-icon>
          鼠标滚轮进行缩放
        </el-text>
      </div>
    </div>
  </el-dialog>
</template>

<script>
import * as THREE from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'
import {STLLoader} from 'three/examples/jsm/loaders/STLLoader.js'
import {InfoFilled} from "@element-plus/icons-vue";

export default {
  components: {InfoFilled},
  emits: ['update:modelValue'],
  props: {
    modelValue: {
      type: Boolean,
      default: false
    },
    url: {
      type: String,
      default: ""
    }
  },
  data() {
    return {
      camera: null,
      renderer: null,
      controls: null,
      width: 0,
    }
  },
  methods: {
    open() {
      if (this.modelValue && !this.renderer) {
        this.init()
      }
    },
    init() {
      this.createRender()
      this.createScene()
      this.createCamera()
      this.loadSTL()
      this.createLight()
      this.createControls()
      this.render()
      this.$nextTick(() => {
        this.$refs.container.appendChild(this.renderer.domElement)
      })
    },
    cleanup() {
      this.$emit('update:modelValue', false)
      if (this.renderer) {
        const element = this.renderer.domElement
        element.parentNode.removeChild(element)
        this.renderer.dispose()
        this.renderer = null
      }
      if (this.controls) {
        this.controls.dispose()
        this.controls = null
      }
      this.scene = null
      this.camera = null
      this.mesh = null
    },
    createScene() {
      this.scene = new THREE.Scene()
    },
    loadSTL() {
      const loader = new STLLoader()
      loader.load(
          this.url,
          geometry => {
            const material = new THREE.MeshLambertMaterial({color: 0xB8B8B8})
            this.mesh = new THREE.Mesh(geometry, material)
            this.mesh.rotation.x = -0.5 * Math.PI
            this.mesh.scale.set(0.9, 0.9, 0.9)
            this.scene.add(this.mesh)
          }
      )
    },
    createLight() {
      const ambientLight = new THREE.AmbientLight(0xffffff, 0.3)
      this.scene.add(ambientLight)
      const spotLight = new THREE.SpotLight(0xffffff)
      spotLight.position.set(150, 150, 150)
      spotLight.castShadow = true
      this.scene.add(spotLight)
    },
    createCamera() {
      this.width = window.innerHeight * 0.8 + 180
      const width = window.innerHeight * 0.8
      const height = window.innerHeight * 0.8
      const k = width / height
      this.camera = new THREE.PerspectiveCamera(35, k, 0.1, 1000)
      this.camera.position.set(0, 500, 0)
      this.camera.lookAt(new THREE.Vector3(10, 40, 0))
      this.scene.add(this.camera)
    },
    createRender() {
      this.renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
      })
      this.renderer.setSize(window.innerHeight * 0.8, window.innerHeight * 0.8)
      this.renderer.shadowMap.enabled = true
      this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
      this.renderer.setClearColor(0xEFEFF0, 1)
    },
    render() {
      if (this.renderer && this.camera) {
        this.renderer.render(this.scene, this.camera);
        requestAnimationFrame(this.render);
      }
    },
    createControls() {
      this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    }
  }
}

</script>

<style>
</style>
